package lumis.util.converter;

import java.util.Locale;

import lumis.portal.stability.StableMinor;

/**
 * This is a abstract converter where a default locale and pattern may be specified and has
 * fallback conversion capability.
 * <p>
 * If the convertion implementation does not support the requested type, this
 * converter falls back to try to convert using
 * {@link ConvertUtil#convert(Class, Object, Locale, String)}.
 * 
 * @version $Revision: 13082 $ $Date: 2011-05-27 16:03:12 -0300 (Fri, 27 May 2011) $
 * @since 4.0.4
 */
@StableMinor(version = "6.0", sinceVersion = "4.0")
public abstract class AbstractConverter implements IConverter
{
	/** 
	 * The default locale to be used in this converter.
	 * @since 4.0.4
	 */
	private Locale defaultLocale;
	
	/**
	 * The default pattern to be used in this converter. 
	 * @since 4.0.4
	 */
	private String defaultPattern;
	
	/**
	 * Indicates if the registered converters should be tried if this converter
	 * does not know how to complete the conversion.
	 * @since 4.0.11
	 */
	private boolean fallbackToRegisteredConverters = true;

	public Locale getDefaultLocale()
	{
		return defaultLocale;
	}

	public void setDefaultLocale(Locale defaultLocale)
	{
		this.defaultLocale = defaultLocale;
	}

	public String getDefaultPattern()
	{
		return defaultPattern;
	}

	public void setDefaultPattern(String defaultPattern)
	{
		this.defaultPattern = defaultPattern;
	}

	protected boolean doesNullValueReturnNull()
	{
		return true;
	}
	
	protected boolean doesValueInstanceOfTypeReturnValue()
	{
		return true;
	}
	
	/**
	 * Indicates if the registered converters should be tried if this converter
	 * does not know how to complete the conversion.
	 * @return true if should fallback to registered converters, false otherwise.
	 * @since 4.0.11
	 */
	protected boolean shouldFallbackToRegisteredConverters()
	{
		return fallbackToRegisteredConverters;
	}

	/**
	 * Sets if the registered converters should be tried if this converter
	 * does not know how to complete the conversion.
	 * <p>
	 * Note that if this property is set to <code>false</code>, it is important
	 * <b>not</b> to fallback to the registered converters, since it may cause 
	 * an infinite loop.
	 * @param fallbackToRegisteredConverters the value to set.
	 * @since 4.0.11
	 */
	protected void setFallbackToRegisteredConverters(boolean fallbackToRegisteredConverters)
	{
		this.fallbackToRegisteredConverters = fallbackToRegisteredConverters;
	}

	/**
	 * Converts the specified input object into an output object of the
	 * specified type.
	 *
	 * @param <T> generics support for the data type specified.
	 * @param type data type to which the value should be converted.
	 * @param value the input value to be converted.
	 * @param locale locale to be used during conversion, if necessary.
	 * @param pattern converter specific conversion pattern, to be used during conversion, if necessary.
	 * @throws UnsupportedConversionTypeException if this converter does not supports converting to the specified type.
	 * @since 4.0.4
	 */
	protected abstract <T> T doConversion(Class<T> type, Object value, Locale locale, String pattern) throws UnsupportedConversionTypeException;
	
	@SuppressWarnings("unchecked")
	public <T> T convert(Class<T> type, Object value, Locale locale, String pattern)
	{
		// set default values if null
		if (locale == null)
			locale = defaultLocale;
		if (pattern == null)
			pattern = defaultPattern;
		
		if (value == null)
		{
			if (doesNullValueReturnNull())
				return null;
		}
		else
		{
			if (doesValueInstanceOfTypeReturnValue())
			{
				if (type.isAssignableFrom(value.getClass()))
					return (T)value;
			}
		}
		
		try
		{
			return doConversion(type, value, locale, pattern);
		}
		catch (UnsupportedConversionTypeException e)
		{
			if (shouldFallbackToRegisteredConverters())
				return ConvertUtil.convert(type, value, locale, pattern);
			else
				throw e;
		}
	}

	public <T> T convert(Class<T> type, Object value, Locale locale)
	{
		return convert(type, value, locale, defaultPattern);
	}

	public <T> T convert(Class<T> type, Object value)
	{
		return convert(type, value, defaultLocale, defaultPattern);
	}

}
